import { unzipSync } from "fflate";
import {
  chmodSync,
  existsSync,
  mkdirSync,
  readFileSync,
  writeFileSync,
} from "fs";
import { join, resolve, sep } from "path";

import { extractSignatureBlock } from "../node/sign.js";
import { getLogger } from "../shared/log.js";

interface UnpackOptions {
  mcpbPath: string;
  outputDir?: string;
  silent?: boolean;
}

export async function unpackExtension({
  mcpbPath,
  outputDir,
  silent,
}: UnpackOptions): Promise<boolean> {
  const logger = getLogger({ silent });
  const resolvedMcpbPath = resolve(mcpbPath);

  if (!existsSync(resolvedMcpbPath)) {
    logger.error(`ERROR: MCPB file not found: ${mcpbPath}`);
    return false;
  }

  const finalOutputDir = outputDir ? resolve(outputDir) : process.cwd();

  if (!existsSync(finalOutputDir)) {
    mkdirSync(finalOutputDir, { recursive: true });
  }

  try {
    const fileContent = readFileSync(resolvedMcpbPath);
    const { originalContent } = extractSignatureBlock(fileContent);

    // Parse file attributes from ZIP central directory
    const fileAttributes = new Map<string, number>();
    const isUnix = process.platform !== "win32";

    if (isUnix) {
      // Parse ZIP central directory to extract file attributes
      const zipBuffer = originalContent;

      // Find end of central directory record
      let eocdOffset = -1;
      for (let i = zipBuffer.length - 22; i >= 0; i--) {
        if (zipBuffer.readUInt32LE(i) === 0x06054b50) {
          eocdOffset = i;
          break;
        }
      }

      if (eocdOffset !== -1) {
        const centralDirOffset = zipBuffer.readUInt32LE(eocdOffset + 16);
        const centralDirEntries = zipBuffer.readUInt16LE(eocdOffset + 8);

        let offset = centralDirOffset;

        for (let i = 0; i < centralDirEntries; i++) {
          if (zipBuffer.readUInt32LE(offset) === 0x02014b50) {
            const externalAttrs = zipBuffer.readUInt32LE(offset + 38);
            const filenameLength = zipBuffer.readUInt16LE(offset + 28);
            const filename = zipBuffer.toString(
              "utf8",
              offset + 46,
              offset + 46 + filenameLength,
            );

            // Extract Unix permissions from external attributes (upper 16 bits)
            const mode = (externalAttrs >> 16) & 0o777;
            if (mode > 0) {
              fileAttributes.set(filename, mode);
            }

            const extraFieldLength = zipBuffer.readUInt16LE(offset + 30);
            const commentLength = zipBuffer.readUInt16LE(offset + 32);
            offset += 46 + filenameLength + extraFieldLength + commentLength;
          } else {
            break;
          }
        }
      }
    }

    const decompressed = unzipSync(originalContent);

    for (const relativePath in decompressed) {
      if (Object.prototype.hasOwnProperty.call(decompressed, relativePath)) {
        const data = decompressed[relativePath];
        const fullPath = join(finalOutputDir, relativePath);

        // Prevent zip slip attacks by validating the resolved path
        const normalizedPath = resolve(fullPath);
        const normalizedOutputDir = resolve(finalOutputDir);
        if (
          !normalizedPath.startsWith(normalizedOutputDir + sep) &&
          normalizedPath !== normalizedOutputDir
        ) {
          throw new Error(`Path traversal attempt detected: ${relativePath}`);
        }

        const dir = join(fullPath, "..");
        if (!existsSync(dir)) {
          mkdirSync(dir, { recursive: true });
        }
        writeFileSync(fullPath, data);

        // Restore Unix file permissions if available
        if (isUnix && fileAttributes.has(relativePath)) {
          try {
            const mode = fileAttributes.get(relativePath);
            if (mode !== undefined) {
              chmodSync(fullPath, mode);
            }
          } catch (error) {
            // Silently ignore permission errors
          }
        }
      }
    }

    logger.log(`Extension unpacked successfully to ${finalOutputDir}`);
    return true;
  } catch (error) {
    if (error instanceof Error) {
      logger.error(`ERROR: Failed to unpack extension: ${error.message}`);
    } else {
      logger.error("ERROR: An unknown error occurred during unpacking.");
    }
    return false;
  }
}
